/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */


#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>

#include "utils.h"
#include "celix_utils.h"
#include "celix_utils_private_constants.h"

#ifdef __APPLE__
#include "memstream/open_memstream.h"
#else
#include <stdio.h>
#endif

unsigned int utils_stringHash(const void* strPtr) {
    return celix_utils_stringHash((const char*)strPtr);
}

int utils_stringEquals(const void* string, const void* toCompare) {
    return celix_utils_stringEquals((const char*)string, (const char*)toCompare);
}

unsigned int celix_utils_stringHash(const char* string) {
    if (string == NULL) {
        return 0;
    }
    unsigned int hc = 5381;
    char ch;
    while((ch = *string++) != '\0'){
        hc = (hc << 5) + hc + ch;
    }
    return hc;
}

bool celix_utils_stringEquals(const char* a, const char* b) {
    if (a == b) {
        return true;
    } else if (a == NULL || b == NULL) {
        return false;
    } else {
        return strncmp(a, b, CELIX_UTILS_MAX_STRLEN) == 0;
    }
}

bool celix_utils_containsWhitespace(const char* s) {
    if (!celix_utils_isStringNullOrEmpty(s)) {
        for (int i = 0; s[i] != '\0'; ++i) {
            if (isspace(s[i])) {
                return true;
            }
        }
    }
    return false;
};

bool celix_utils_isStringNullOrEmpty(const char* s) {
    return s == NULL || s[0] == '\0';
}

char* celix_utils_makeCIdentifier(const char* s) {
    if (celix_utils_isStringNullOrEmpty(s)) {
        return NULL;
    }
    size_t len = celix_utils_strlen(s);
    char* ns = malloc(len + 2); //+2 for '\0' and an extra _ prefix if needed.
    int i = 0;
    if (isdigit(s[0])) {
        ns[i++] = '_';
    }
    for (size_t j = 0; j < len; j++) {
        if (isalnum(s[j])) {
            ns[i++] = s[j];
        } else {
            ns[i++] = '_';
        }
    }
    ns[i] = '\0';
    return ns;
}


char * string_ndup(const char *s, size_t n) {
    size_t len = strlen(s);
    char *ret;

    if (len <= n) {
        return strdup(s);
    }

    ret = malloc(n + 1);
    strncpy(ret, s, n);
    ret[n] = '\0';
    return ret;
}

static char* celix_utilsTrimInternal(char* string) {
    if (string == NULL) {
        return NULL;
    }

    char* begin = string; //save begin to correctly free in the end.

    char *end;
    // Trim leading space
    while (isspace(*string)) {
        string++;
    }

    // Trim trailing space
    end = string + strlen(string) - 1;
    while(end > string && isspace(*end)) {
        *(end) = '\0';
        end--;
    }

    if (string != begin) {
        //beginning whitespaces -> move char in copy to to begin string
        //This to ensure free still works on the same pointer.
        char* nstring = begin;
        while(*string != '\0') {
            *(nstring++) = *(string++);
        }
        (*nstring) = '\0';
    }

    return begin;
}

char* celix_utils_trim(const char* string) {
    return celix_utilsTrimInternal(celix_utils_strdup(string));
}

char* celix_utils_trimInPlace(char* string) {
    return celix_utilsTrimInternal(string);
}

char* utils_stringTrim(char* string) {
    return celix_utilsTrimInternal(string);
}

bool utils_isStringEmptyOrNull(const char * const str) {
    bool empty = true;
    if (str != NULL) {
        int i;
        for (i = 0; i < celix_utils_strlen(str); i += 1) {
            if (!isspace(str[i])) {
                empty = false;
                break;
            }
        }
    }

    return empty;
}

char* celix_utils_writeOrCreateString(char* buffer, size_t bufferSize, const char* format, ...) {
    char *ret = NULL;
    va_list args;
    va_start(args, format);
    ret = celix_utils_writeOrCreateVString(buffer, bufferSize, format, args);
    va_end(args);
    return ret;
}

char* celix_utils_writeOrCreateVString(char* buffer, size_t bufferSize, const char* format, va_list formatArgs) {
    va_list argCopy;
    va_copy(argCopy, formatArgs);
    int written = vsnprintf(buffer, bufferSize, format, argCopy);
    va_end(argCopy);
    if (written < 0 || written >= bufferSize) {
        //buffer to small, create new string
        char* newStr = NULL;
        int rc = vasprintf(&newStr, format, formatArgs);
        return rc == -1 ? NULL : newStr;
    }
    return buffer;

}

void celix_utils_freeStringIfNotEqual(const char* buffer, char* str) {
    if (str != buffer) {
        free(str);
    }
}

celix_status_t thread_equalsSelf(celix_thread_t thread, bool *equals) {
    celix_thread_t self = celixThread_self();
    *equals = celixThread_equals(self, thread);
    return CELIX_SUCCESS;
}

celix_status_t utils_isNumeric(const char *number, bool *ret) {
    celix_status_t status = CELIX_SUCCESS;
    *ret = true;
    while(*number) {
        if(!isdigit(*number) && *number != '.') {
            *ret = false;
            break;
        }
        number++;
    }
    return status;
}

int utils_compareServiceIdsAndRanking(long svcIdA, long svcRankA, long svcIdB, long svcRankB) {
    return celix_utils_compareServiceIdsAndRanking(svcIdA, svcRankA, svcIdB, svcRankB);
}

int celix_utils_compareServiceIdsAndRanking(long svcIdA, long svcRankA, long svcIdB, long svcRankB) {
    int result;

    if (svcIdA == svcIdB) {
        result = 0;
    } else if (svcRankA != svcRankB) {
        result = svcRankA < svcRankB ? 1 : -1;
    } else { //equal service rank, compare service ids
        result = svcIdA < svcIdB ? -1 : 1;
    }

    return result;
}

double celix_difftime(const struct timespec *tBegin, const struct timespec *tEnd) {
    struct timespec diff;
    if ((tEnd->tv_nsec - tBegin->tv_nsec) < 0) {
        diff.tv_sec = tEnd->tv_sec - tBegin->tv_sec - 1;
        diff.tv_nsec = tEnd->tv_nsec - tBegin->tv_nsec + CELIX_NS_IN_SEC;
    } else {
        diff.tv_sec = tEnd->tv_sec - tBegin->tv_sec;
        diff.tv_nsec = tEnd->tv_nsec - tBegin->tv_nsec;
    }

    return ((double)diff.tv_sec) + diff.tv_nsec * 1.0 /  CELIX_NS_IN_SEC;
}

struct timespec celix_gettime(clockid_t clockId) {
    struct timespec t;
    errno = 0;
    int rc = clock_gettime(clockId, &t);
    if (rc != 0) {
        fprintf(stderr, "Cannot get time from clock_gettime. Error is %s\n", strerror(errno));
    }
    return t;
}

struct timespec celix_delayedTimespec(const struct timespec* time, double delayInSeconds) {
    struct timespec delayedTime;
    if (time != NULL) {
        delayedTime = *time;
    } else {
        delayedTime.tv_nsec = 0;
        delayedTime.tv_sec = 0;
    }

    long seconds = (long)delayInSeconds;
    double nanoseconds = (delayInSeconds - (double)seconds) * CELIX_NS_IN_SEC;
    delayedTime.tv_sec += seconds;
    delayedTime.tv_nsec += (long)nanoseconds;

    if (delayedTime.tv_nsec >= CELIX_NS_IN_SEC) {
        delayedTime.tv_sec += 1;
        delayedTime.tv_nsec -= CELIX_NS_IN_SEC;
    } else if (delayedTime.tv_nsec < 0) {
        delayedTime.tv_sec -= 1;
        delayedTime.tv_nsec += CELIX_NS_IN_SEC;
    }
    
    return delayedTime;
}

double celix_elapsedtime(clockid_t clockId, struct timespec startTime) {
    struct timespec now = celix_gettime(clockId);
    return celix_difftime(&startTime, &now);
}

int celix_compareTime(const struct timespec *a, const struct timespec *b) {
    if (a->tv_sec == b->tv_sec && a->tv_nsec == b->tv_nsec) {
        return 0;
    }
    double diff = celix_difftime(a, b);
    if (diff < 0) {
        return 1;
    }
    return -1;
}

char* celix_utils_strdup(const char *str) {
    if (str != NULL) {
        return strndup(str, CELIX_UTILS_MAX_STRLEN);
    } else {
        return NULL;
    }
}

size_t celix_utils_strlen(const char *str) {
    return str ? strnlen(str, CELIX_UTILS_MAX_STRLEN) : 0;
}

void celix_utils_extractLocalNameAndNamespaceFromFullyQualifiedName(const char *fullyQualifiedName, const char *namespaceSeparator, char **outLocalName, char **outNamespace) {
    assert(namespaceSeparator != NULL);
    if (fullyQualifiedName == NULL) {
        *outLocalName = NULL;
        *outNamespace = NULL;
        return;
    }

    char *cpy = celix_utils_strdup(fullyQualifiedName);

    char *local = NULL;
    char *namespace = NULL;
    size_t namespaceLen;
    FILE *namespaceStream = open_memstream(&namespace, &namespaceLen);

    char *savePtr = NULL;
    char *nextSubStr = NULL;
    char *currentSubStr = strtok_r(cpy, namespaceSeparator, &savePtr);
    bool firstNamespaceEntry = true;
    while (currentSubStr != NULL) {
        nextSubStr = strtok_r(NULL, namespaceSeparator, &savePtr);
        if (nextSubStr != NULL) {
            //still more, so last is part of the namespace
            if (firstNamespaceEntry) {
                firstNamespaceEntry = false;
            } else {
                fprintf(namespaceStream, "%s", namespaceSeparator);
            }
            fprintf(namespaceStream, "%s", currentSubStr);
        } else {
            //end reached current is local name
            local = celix_utils_strdup(currentSubStr);
        }
        currentSubStr = nextSubStr;
    }
    fclose(namespaceStream);
    free(cpy);
    *outLocalName = local;
    if (namespace == NULL) {
      *outNamespace = NULL;
    } else if (strncmp("", namespace, 1) == 0)  {
        //empty string -> set to NULL
        *outNamespace = NULL;
        free(namespace);
    } else {
        *outNamespace = namespace;
    }
}
